import DebugExt from "../base/DebugExt";
import { ScrollItemBase } from "./ScrollItemBase";

const { ccclass, property, disallowMultiple, requireComponent, menu } = cc._decorator;

/** 
 * 虚拟列表组件
 * 实现一个高性能的 ScrollView 组件
 */
@ccclass
@menu('ComponentExt/ScrollViewExt')
@disallowMultiple
@requireComponent(cc.ScrollView)
export default class ScrollViewExt extends cc.Component {

    /** ScrollView 组件（可不填，初始化自动尝试获取当前节点的 ScrollView 组件）*/
    @property(cc.ScrollView)
    public scrollView: cc.ScrollView = null;

    /** slider bar */
    @property(cc.Slider)
    private readonly slider: cc.Slider = null;

    /** 列表项预制体 */
    @property(cc.Prefab)
    private readonly prefabCell: cc.Prefab = null;
    /** 列表项预制体节点（可不填，由Prefab实例化） */
    @property(cc.Node)
    private prefabCellNode: cc.Node = null;

    @property({
        tooltip: "缓冲区 item 个数"
    })
    private readonly bufferZoneCount: number = 2;

    @property({
        tooltip: "item 间距"
    })
    private readonly spacing: number = 0;

    @property({
        tooltip: "开头间距"
    })
    private readonly spaceStart: number = 0;

    @property({
        tooltip: "末尾间距"
    })
    private readonly spaceEnd: number = 0;


    // 节点容器
    private content: cc.Node = null;
    // 对象集
    private items: cc.Node[] = [];
    // 数据集
    private _itemDatas: ReadonlyArray<any>;
    // 绘制区域长度
    private _bufferZone: number = 0;
    // 窗口可见节点数
    private _visibleCnt: number = 0;
    // 窗口可渲染节点数
    private _spawnCount: number = 0;
    // 节点数据总数
    private _totalCount: number = 0;
    // 预制体高度
    private itemHeight: number = 0;
    // 预制体宽度
    private itemWidth: number = 0;

    // last Content Pos X
    private lastContentPosX: number = 0;
    // last Content Pos Y
    private lastContentPosY: number = 0;

    // 是否初始化
    private isInit: boolean = false;
    // 是否正在更新
    private _isScrolling: boolean = false;
    // 更新计时器
    private updateTimer: number = 0;
    // 更新间隔
    private updateInterval: number = 0.03;


    protected onLoad(): void {
        if (!this.scrollView)
            this.scrollView = this.node.getComponent(cc.ScrollView);
        if (!this.prefabCellNode && this.prefabCell)
            this.prefabCellNode = cc.instantiate(this.prefabCell);
        if (this.prefabCellNode && this.prefabCellNode.parent)
            this.prefabCellNode.removeFromParent(false);
        if (!this.scrollView)
            DebugExt.warn('【ScrollViewExt】 ScrollView 不能为空！');
        else
            this.content = this.scrollView.content;

        if (!this.prefabCellNode)
            DebugExt.warn('【ScrollViewExt】 列表项预制体节点 不能为空！');
        else {
            this.itemHeight = this.prefabCellNode.height;
            this.itemWidth = this.prefabCellNode.width;
            DebugExt.log('【ScrollViewExt】 列表项预制体节点 高度：', this.itemHeight, '宽度：', this.itemWidth);
        }

        if (this.slider)
            this.slider.progress = 0;

        this.scrollView.node.on('scrolling', this.onScrolling, this);
        this.scrollView.node.on('scroll-ended', this.onScrollEnded, this);
    }

    protected onDestroy(): void {
        this.scrollView.node.off('scrolling', this.onScrolling, this);
        this.scrollView.node.off('scroll-ended', this.onScrollEnded, this);
    }

    protected start(): void {
        if (this.scrollView.horizontal) {
            this._bufferZone = this.content.parent.width;
            this.content.setAnchorPoint(0, 0.5);
        }
        else {
            this._bufferZone = this.content.parent.height;
            this.content.setAnchorPoint(0.5, 1);
        }
    }

    private onScrolling() {
        if (!this._isScrolling) {
            this._isScrolling = true;
        }
    }
    private onScrollEnded() {
        this._isScrolling = false;
        // DebugExt.log('【ScrollViewExt】 onScrollEnded');
        this.updateItems(true);
        this.items.forEach(item => item.getComponent(ScrollItemBase)?.forceRender());
        this.content.setPosition(this.lastContentPosX, this.lastContentPosY);
    }

    protected update(dt: number): void {
        if (!(this._isScrolling || this.scrollView.isScrolling()) || !this.isInit) return;
        this.updateTimer += dt;
        if (this.updateTimer < this.updateInterval) return;
        this.updateTimer = 0;

        this.updateItems(false);
    }
    // get item position in scrollview's node space
    private getPositionInView(item: cc.Node) {
        let worldPos = item.parent.convertToWorldSpaceAR(item.position);
        let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);
        return viewPos;
    }
    // private update items
    private updateItems(stat: boolean) {
        let items = this.items;
        let buffer = this._bufferZone;
        // scrolling direction
        if (this.scrollView.vertical) {
            let isDown = this.content.position.y < this.lastContentPosY;
            let offset = (this.itemHeight + this.spacing) * items.length;
            for (let i = 0; i < items.length; ++i) {
                let viewPos = this.getPositionInView(items[i]);
                let item = items[i].getComponent(ScrollItemBase);
                if (isDown) {
                    // if away from buffer zone and not reaching top of content
                    if (viewPos.y < -buffer / 2 - (this.itemHeight + this.spacing) / 2 && items[i].position.y + offset < 0) {
                        const posyOld = items[i].position.y;
                        const y = posyOld + offset;
                        items[i].setPosition(0, y, 0);
                        const idx = item.idx - items.length;
                        items[i].getComponent(ScrollItemBase).initData(idx, this._itemDatas[idx], stat);
                        // DebugExt.log('【ScrollViewExt】 ' + items[i].name + ' 更新位置：', posyOld, ' => ', y, " isDown:", isDown, ' idx:', idx);
                    }
                } else {
                    // if away from buffer zone and not reaching bottom of content
                    if (viewPos.y > buffer / 2 + (this.itemHeight + this.spacing) / 2 && items[i].position.y - offset > -this.content.height) {
                        const posyOld = items[i].position.y;
                        const y = posyOld - offset;
                        items[i].setPosition(0, y, 0);
                        const idx = item.idx + items.length;
                        items[i].getComponent(ScrollItemBase).initData(idx, this._itemDatas[idx], stat);
                        // DebugExt.log('【ScrollViewExt】 ' + items[i].name + ' 更新位置：', posyOld, ' => ', y, " isDown:", isDown, ' idx:', idx);
                    }
                }
            }
            // update lastContentPosY
            this.lastContentPosY = this.content.position.y;
        }
        else if (this.scrollView.horizontal) {
            let isRight = this.content.position.x > this.lastContentPosX;
            let offset = (this.itemWidth + this.spacing) * items.length + this.spaceStart;
            for (let i = 0; i < items.length; ++i) {
                let viewPos = this.getPositionInView(items[i]);
                let item = items[i].getComponent(ScrollItemBase);
                if (isRight) {
                    // if away from buffer zone and not reaching top of content
                    if (viewPos.x > buffer / 2 + (this.itemWidth + this.spacing) / 2 && items[i].position.x - offset > 0) {
                        const x = items[i].position.x - offset;
                        items[i].setPosition(x, 0, 0);
                        const idx = item.idx + items.length;
                        items[i].getComponent(ScrollItemBase).initData(idx, this._itemDatas[idx], false);
                    }
                } else {
                    // if away from buffer zone and not reaching bottom of content
                    if (viewPos.x < -buffer / 2 - (this.itemWidth + this.spacing) / 2 && items[i].position.x + offset < this.content.width) {
                        const x = items[i].position.x + offset;
                        items[i].setPosition(x, 0, 0);
                        const idx = item.idx - items.length;
                        items[i].getComponent(ScrollItemBase).initData(idx, this._itemDatas[idx], false);
                    }
                }
            }
            // update lastContentPosX
            this.lastContentPosX = this.content.position.x;
        }

        this.updateSliderPos();
    }

    /**
     * 初始化更新数据源
     * @param itemDatas 数据源
     * @param startIndex 起始索引
     */
    public init(itemDatas: ReadonlyArray<any>, startIndex: number = 0) {
        this._totalCount = itemDatas.length;
        this._itemDatas = itemDatas;
        this.lastContentPosY = 0;
        this.lastContentPosX = 0;

        if (!this.scrollView || !this.prefabCellNode)
            return;
        if (this.scrollView.vertical && this.scrollView.horizontal) {
            DebugExt.log('【ScrollViewExt】 暂不支持双向 ScrollView');
            return;
        }
        // if (CC_PREVIEW) {
        //     // 整理itemDatas数据的id,并存储临时数组，用于打印日志
        //     let tempDatas = [];
        //     for (let i = 0; i < itemDatas.length; i++) {
        //         tempDatas.push(itemDatas[i].id);
        //     }
        //     DebugExt.log('【ScrollViewExt】 初始化', tempDatas, " 数据总量：", this._totalCount, " startIndex:", startIndex);
        // }

        // refresh total spawn count
        if (this.scrollView.horizontal) {
            this._visibleCnt = Math.floor(this._bufferZone / (this.itemWidth + this.spacing));
            this._spawnCount = this._visibleCnt + this.bufferZoneCount;
        }
        else {
            this._visibleCnt = Math.floor(this._bufferZone / (this.itemHeight + this.spacing));
            this._spawnCount = this._visibleCnt + this.bufferZoneCount;
        }
        if (this._spawnCount > this._totalCount) {
            this._spawnCount = this._totalCount;
        }
        if (this._spawnCount < this.items.length) {
            this.items.forEach(item => item.active = false);
        }

        // refresh shown start index
        const nstIdx = Math.min(startIndex, this._totalCount - this._spawnCount);
        let offCnt = 0;
        if (startIndex + this._visibleCnt > this._totalCount)
            offCnt = this.bufferZoneCount;
        else if (this._totalCount - startIndex - this._visibleCnt < this.bufferZoneCount) {
            offCnt = this.bufferZoneCount - (this._totalCount - startIndex - this._visibleCnt);
        }

        if (this.scrollView.vertical) {
            const height = this._totalCount * (this.itemHeight + this.spacing) + this.spacing + this.spaceStart + this.spaceEnd;
            this.content.setContentSize(this.content.width, height);
        }
        else if (this.scrollView.horizontal) {
            const width = this._totalCount * (this.itemWidth + this.spacing) + this.spacing + this.spaceStart + this.spaceEnd;
            this.content.setContentSize(width, this.content.height);
        }
        // DebugExt.log('【ScrollViewExt】 ContentSize ', this.content.getContentSize(), " 数据总量：", this._totalCount);

        for (let i = 0; i < this._spawnCount; ++i) {
            let item = this.items[i];
            if (!item) {
                item = cc.instantiate(this.prefabCellNode);
                this.content.addChild(item);
                this.items.push(item);
            }
            item.active = true;

            const idx = i + nstIdx;
            item.getComponent(ScrollItemBase).initData(idx, this._itemDatas[idx], true);
            if (this.scrollView.vertical) {
                const posY = -item.height * (0.5 + idx) - this.spacing * (idx + 1) - this.spaceStart;
                item.setPosition(0, posY);
                if (i == offCnt) {
                    const offsetY = -posY - item.height * 0.5;
                    this.scrollView.scrollToOffset(cc.v2(0, offsetY), 0);
                }
            }
            else if (this.scrollView.horizontal) {
                const posX = item.width * (idx + 0.5) + this.spacing * (idx + 1) + this.spaceStart;
                item.setPosition(posX, 0);
                if (i == offCnt) {
                    const offsetX = posX - item.width * 0.5;
                    this.scrollView.scrollToOffset(cc.v2(offsetX, 0), 0);
                }
            }
        }

        this.updateSliderPos();
        DebugExt.log("ScrollViewExt 窗口可渲染节点数:", this._spawnCount, " 缓冲节点数目:", this.bufferZoneCount);
        this.isInit = true;
    }

    // 更新 slider 位置
    private updateSliderPos() {
        if (this.slider) {
            // 获取 cc.scrollview 的滑动百分比
            if (this.scrollView.horizontal) {
                const scrollX = this.scrollView.getScrollOffset().x;
                const scrollViewWidth = this.scrollView.node.width;
                const contentWidth = this.content.width;
                let percent = (scrollX / (contentWidth - scrollViewWidth));
                percent = Math.max(0, Math.min(1, percent));
                this.slider.progress = percent;
            }
            else if (this.scrollView.vertical) {
                const scrollY = this.scrollView.getScrollOffset().y;
                const scrollViewHeight = this.scrollView.node.height;
                const contentHeight = this.content.height;
                let percent = (scrollY / (contentHeight - scrollViewHeight));
                percent = Math.max(0, Math.min(1, percent));
                this.slider.progress = percent;
            }

        }
    }

}
